/*
 * @(#)OgnlUtil.java $version 2010. 8. 11
 *
 * Copyright 2007-2010 NHN Corp. All rights Reserved. 
 * NHN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package com.nhncorp.usf.core.util;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.collections15.map.LRUMap;

import com.nhncorp.lucy.common.data.DataHandler;

import ognl.Ognl;
import ognl.OgnlContext;
import ognl.OgnlException;
import ognl.SimpleNode;

/**
 * Ognl Utility.
 *
 * @author Web Platform Development Team
 */
public class OgnlUtil {
	private static final Map<String, SimpleNode> EXPRESSION_CACHE = Collections.synchronizedMap(new LRUMap<String, SimpleNode>(
		65536)); // is not excessive?

	/**
	 * expression 파싱후 {@link SimpleNode} 반환.
	 *
	 * @throws OgnlException expression 파싱실패시 던져지는 예외.
	 * @return SimpleNode
	 * @param expression String
	 */ 
	public static SimpleNode evaluate(String expression) throws OgnlException {
		SimpleNode simpleNode = EXPRESSION_CACHE.get(expression);
		
		if (simpleNode == null) {
			simpleNode = (SimpleNode)Ognl.parseExpression(expression);
			EXPRESSION_CACHE.put(expression, simpleNode);
		}
		
		return simpleNode;
	}

	/**
	 * expression 파싱 후 값 반환.
	 * 
	 * @param <T> T
	 * @param onglContext OgnlContext
	 * @param expression 평가하고자 하는 expression
	 * @param root Object
	 * @throws OgnlException the OgnlException
	 * @return T
	 * 
	 */
	@SuppressWarnings("unchecked")
	public static <T> T evaluateAndGet(OgnlContext onglContext, String expression, Object root) throws OgnlException {
		SimpleNode simpleNode = evaluate(expression);
		return (T)Ognl.getValue(simpleNode, onglContext, root);
	}

	/**
	 * expression 파싱 후 값 반환.
	 *
	 * @param expression 평가하고자 하는 expression
	 * @param root Object
	 * @param <T> generic
	 * @throws Exception the Exception
	 * @return T 
	 */
	@SuppressWarnings("unchecked")
	public static <T> T evaluateAndGet(String expression, Object root) throws Exception {
		OgnlContext ognlContext = (OgnlContext)Ognl.createDefaultContext(null);
		return (T)evaluateAndGet(ognlContext, expression, root);
	}

	/**
	 * expression 내의 <code>${}</code>의 값을 치환.
	 * <code>${}</code>에 지정된 속성은 {@link DataMap} 에 저장된 속성이어야 한다.
	 * 
	 * @deprecated use {@link #translate(String, Map)}.
	 * @return String
	 * @param expression String
	 * @param dataMap Map<String, Object>
	 * @throws Exception the Exception
	 */ 
	@Deprecated
	public static String translateVariables(String expression, Map<String, Object> dataMap) throws Exception {
		return translate(expression, dataMap);
	}

	private static Pattern pattern = Pattern.compile("\\$\\{(\\w*)\\}");

	/**
	 * expression 내의 <code>${}</code>의 값을 치환.
	 * <code>${}</code>에 지정된 속성은 {@link DataMap} 에 저장된 속성이어야 한다.
	 * 
	 * @return String
	 * @param expression String
	 * @param dataMap Map<String, Object>
	 * @throws Exception the Exception
	 */
	public static String translate(String expression, Map<String, Object> dataMap) throws Exception {
		StringBuilder builder = new StringBuilder(expression.length() << 1);
		Matcher matcher = pattern.matcher(expression);
		int start = 0;
		
		while (matcher.find()) {
			builder.append(expression.substring(start, matcher.start()));
			builder.append(evaluate(matcher.group(1), dataMap));
			start = matcher.end();
		}
		
		return builder.append(expression.substring(start)).toString();
	}

	/**
	 * Evaluate.
	 * 
	 * @param var the var
	 * @param dataMap the data map
	 * 
	 * @return the object
	 * 
	 * @throws Exception the exception
	 */
	@SuppressWarnings("unchecked")
	private static Object evaluate(String var, Map<String, Object> dataMap) throws Exception {
		Object object;
		
		if (var == null) {
			return null;
		}

		if (var.indexOf("[") == -1) {
			object = OgnlUtil.evaluateAndGet(var, dataMap);
		} else {
			String variable = StringUtil.substringBefore(var, "[");
			String value = StringUtil.substringBetween(var, "'");

			if (variable == null) {
				throw new IllegalArgumentException("Invalid Expression. variable is null");
			}
			if (value == null) {
				throw new IllegalArgumentException("Invalid Expression. key name is null");
			}
			
			value = value.trim();

			object = OgnlUtil.evaluateAndGet(variable, dataMap);
			
			if (object instanceof Map) {
				object = ((Map)object).get(value);
			} else if (object instanceof DataHandler) {
				object = ((DataHandler)object).get(value);
			} else if (object instanceof List) {
				object = ((List)object).get(Integer.parseInt(value));
			} else if (object instanceof Object[]) {
				object = ((Object[])object)[Integer.parseInt(value)];
			}
		}
		
		return object;
	}
}
